﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SmokinAces.Object_Classes.Objects
{
	/// <summary>
	/// Defines a gneric particle system
	/// </summary>
	public abstract class Particle_System :Game_Object, Draw_Object, Logic_Object
	{
        private Sort_Delegate m_Sort_Delegate;

        public Particle_System(Main_Engine dad)
		{
			parent = dad;

            //------Since it belongs to it's own layer it doesn't really need to be sorted.
            m_Sort_Delegate = new Sort_Delegate(0, Draw);
		}

		protected class Pool : IEnumerable<Particle>
		{
			private int deadCount = 0;
			private List<Particle> particles = new List<Particle>();

			public PtclType NewParticle<PtclType>() where PtclType : Particle, new()
			{
				if(deadCount == 0)
				{
					PtclType particle = new PtclType();
					particles.Add(particle);
					return particle;
				}
				else
				{
					int index = particles.FindIndex(p => !p.Alive);
					if(!(particles[index] is PtclType))
						particles[index] = new PtclType();

					PtclType particle = (PtclType)particles[index];
					particle.Neutralize();
					deadCount--;
					return particle;
				}
			}
			public void RemoveParticle(Particle particle)
			{
				int index = particles.FindIndex(p => object.ReferenceEquals(p, particle));
				particles[index].Alive = false;
				deadCount++;
			}
			public IEnumerator<Particle> GetEnumerator()
			{
				foreach(Particle particle in particles)
					if(particle.Alive) yield return particle;
			}
			System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
			{
				return this.GetEnumerator();
			}

			public void RemoveAll()
			{
				deadCount = 0;
				particles.Clear();
			}
		}
		protected abstract class Particle
		{
			public Vector2 Velocity;
			public Vector2 Location;
			public Particle_System System { get; set; }

			public void AddVelocity(Vector2 deltaV)
			{
				Velocity += deltaV;
			}
			public void AddLocation(Vector2 deltaP)
			{
				Location += deltaP;
			}

			public virtual bool Alive { get; set; }
			public virtual Vector2 DeltaVel(GameTime time) { return Vector2.Zero; }
			public virtual void Neutralize()
			{
				Velocity = default(Vector2);
				Location = default(Vector2);
			}
			public virtual void Activate(Vector2 pos, Vector2 vel, GameTime gameTime)
			{
				Velocity = vel;
				Location = pos;
			}

			public abstract bool TakePulse(GameTime time);
			public abstract void Draw(GameTime gameTime,  int index);
		}

		protected Pool partList = new Pool();
		protected T addParticle<T>(Vector2 p, GameTime t) where T : Particle, new()
		{
            
			var ptcl = partList.NewParticle<T>();
			ptcl.Activate(p, Vector2.Zero, t);
			ptcl.System = this;
			return ptcl;
            
            //return null;
		}
		protected T addParticle<T>(Vector2 p, Vector2 v, GameTime t) where T : Particle, new()
		{
            
			var ptcl = partList.NewParticle<T>();
			ptcl.Activate(p, v, t);
			ptcl.System = this;
			return ptcl;
            
            //return null;
		}
		protected void removeParticle(Particle p)
		{
			partList.RemoveParticle(p);
		}

        #region Logic_Object Members
        public virtual void Update(GameTime gameTime)
		{
			Queue<Particle> oldQueue = new Queue<Particle>();
			foreach(Particle p in partList)
			{
				if(!p.Alive) continue;
				p.AddLocation(p.Velocity * (float)gameTime.ElapsedGameTime.TotalSeconds);
				p.AddVelocity(p.DeltaVel(gameTime) * (float)gameTime.ElapsedGameTime.TotalSeconds);
				p.Alive = p.TakePulse(gameTime);
				oldQueue.Enqueue(p);
			}
			foreach(Particle p in oldQueue) removeParticle(p);
		}
        public bool startUpdate()
        {
            parent.add_Update_Object(Update);
            return true;
        }
        public bool stopUpdate()
        {
            parent.remove_Update_Object(Update);
            return true;
        }
		protected abstract void OnParticleRemoved(Particle p, GameTime t);
		#endregion
        #region Game_Object Members
        public override bool dispose()
        {
            return stopUpdate() && stopDraw();
        }
        #endregion
        #region Draw_Object Members
        public virtual void Draw(GameTime gameTime, GraphicsDevice device)
		{
			int elements = 0;
			foreach(Particle particle in partList)
				if(particle.Alive)
				{
					particle.Draw(gameTime, elements);
					elements++;
				}
        }
        public bool startDraw()
        {
            parent.Draw_Functions[9].Add(m_Sort_Delegate);
            return true;
        }
        public bool stopDraw()
        {
            parent.Draw_Functions[9].Remove(m_Sort_Delegate);
            return true;
        }
		//protected abstract void DrawBuffer(GraphicsDevice device, int elements);
        #endregion

		protected void clearParticles()
		{
			partList.RemoveAll();
		}
	}

	public class Rain_System : Particle_System
	{
		float xmax;
		float ymax;
		Vector2 src;

		private class Rain_Particle : Particle
		{
			public Vector2 Origin { get; private set; }
			public Rain_Particle() { }
			public override bool TakePulse(GameTime time)
			{
				float xmax = ((Rain_System)System).xmax;
				float ymax = ((Rain_System)System).ymax;
				if(Location.X > xmax || Location.Y > ymax)
					return false;
				else
					return true;
			}
            public override void Draw(GameTime gameTime, int index)
            {
				
            }
			public override void Activate(Vector2 pos, Vector2 vel, GameTime gameTime)
			{
				Origin = pos;
				base.Activate(pos, vel, gameTime);
			}
		}
        public Rain_System(Main_Engine game)
			: base(game)
		{
		}
		public override void Update(GameTime gameTime)
		{
			xmax = (float)parent.GraphicsDevice.Viewport.Width + 3f;
			ymax = (float)parent.GraphicsDevice.Viewport.Height + 3f;
			base.Update(gameTime);
		}
		//protected override void DrawBuffer(GraphicsDevice device, int elements)
		//{
		//	VertexPositionColor[] data = new VertexPositionColor[elements];
		//	buffer.GetData<VertexPositionColor>(data);

		//	var ps = device.RenderState.PointSize;
		//	device.RenderState.PointSize = 3;
		//	device.DrawUserPrimitives(PrimitiveType.LineList, data, 0, data.Length / 2);
		//	device.RenderState.PointSize = ps;
		//}
		protected override void OnParticleRemoved(Particle p, GameTime t)
		{
			var rp = p as Rain_Particle;
			addParticle<Rain_Particle>(rp.Origin, t);
		}
		public void Start(GameTime time, float dist)
		{
			xmax = (float)parent.GraphicsDevice.Viewport.Width + 3f;
			ymax = (float)parent.GraphicsDevice.Viewport.Height + 3f;

			//float t = (float)time.TotalRealTime.TotalSeconds;
			float dx = xmax / dist;
			float dy = ymax / dist;
			int len = (int)dist;
			for(int i = 0; i < len; i++)
				for(int j = 0; j < len; j++)
					this.addParticle<Rain_Particle>(new Vector2(((float)i) * dx, ((float)j) * dy), time);
		}
		public void Stop()
		{
			base.clearParticles();
		}
	}
}
